package com.sparrow.source.common.utils;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 雪花算法ID 生成器
 *
 * @author bryceyung
 */
public class SnowflakeIdGenerator {

    // 自定义起始时间戳
    private final static long START_TIMESTAMP = 1640995200000L;

    // 序列号占用位数
    private final static long SEQUENCE_BIT = 8L;

    // 机器标识占用位数
    private final static long MACHINE_BIT = 2L;

    // 数据中心占用位数
    private final static long DATACENTER_BIT = 2L;

    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    private final static long MAX_MACHINE_ID = -1L ^ (-1L << MACHINE_BIT);

    private final static long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_BIT);

    private static long currentMillis = System.currentTimeMillis();

    private final AtomicLong sequence = new AtomicLong(0);

    private final long machineId;

    private final long datacenterId;

    public SnowflakeIdGenerator(long machineId, long datacenterId) {
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException("machineId out of range");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId out of range");
        }
        this.machineId = machineId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < currentMillis) {
            throw new RuntimeException("clock is move backwards");
        }
        if (currentMillis == timestamp) {
            long nextSequence = sequence.getAndIncrement() & MAX_SEQUENCE;
            if (nextSequence == 0) {
                timestamp = tilNextMillis(currentMillis);
            }
        } else {
            currentMillis = timestamp;
            sequence.set(0);
        }

        return (timestamp - START_TIMESTAMP) << (DATACENTER_BIT + MACHINE_BIT + SEQUENCE_BIT) |
                datacenterId << (MACHINE_BIT + SEQUENCE_BIT) |
                machineId << SEQUENCE_BIT |
                sequence.get();
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}